// file:src/kernel/fs/fsal/fsal.c
// autor:jiangxinpeng
// time:2021.3.11
// copyright:(C) by jiangxinpeng,All right are reserved.
#include <arch/module.h>
#include <os/fsal.h>
#include <os/file.h>
#include <os/dir.h>
#include <os/account.h>
#include <os/path.h>
#include <os/debug.h>
#include <os/fs.h>
#include <os/fifo.h>
#include <os/kernelif.h>
#include <os/memcache.h>
#include <os/diskman.h>
#include <lib/list.h>
#include <lib/string.h>
#include <lib/stdio.h>
#include <lib/cpio.h>
#include <lib/unistd.h>
#include <sys/fcntl.h>

int FsalInit()
{
    if (FsalFileTableInit() < 0)
        return -1;
    if (FsalDirTableInit() < 0)
        return -1;
    if (FsalPathTableInit() < 0)
        return -1;

    // mount root
    if (FsalDiskMountInit() < 0)
    {
        KPrint("[fsal] disk mount failed!\n");
        return -1;
    }
    KPrint("[fsal] disk mount ok!\n");

    if (KFileMkDir(HOME_DIR_PATH, 0) < 0)
        KPrint(PRINT_WARNNING "Fsal: create dir %s failed!\n", HOME_DIR_PATH);
    if (KFileMkDir(ACCOUNT_DIR_PATH, 0) < 0)
        KPrint(PRINT_WARNNING "Fsal: create dir %s failed!\n", ACCOUNT_DIR_PATH);
    if (KFileMkDir(SYS_DIR_PATH, 0) < 0)
        KPrint(PRINT_WARNNING "Fsal: create dir %s failed!\n", SYS_DIR_PATH);
    if (KFileMkDir(DEV_DIR_PATH, 0) < 0)
        KPrint(PRINT_WARNNING "Fsal: create dir %s failed!\n", DEV_DIR_PATH);

    // mount device dir
    if (fsif.mount("/dev/ram0", DEV_DIR_PATH, "devfs", 0) < 0)
    {
        KPrint("Fsal: mount path %s failed!\n", DEV_DIR_PATH);
        return -1;
    }

    // mount fifo dir
    if (fsif.mount("/dev/ram0", FIFO_DIR_PATH, "fifofs", 0) < 0)
    {
        KPrint("Fsal: mount path %s failed!\n", FIFO_DIR_PATH);
        return -1;
    }
    return 0;
}

static void CpioExtractFromMemory(void *archive, const char *dir)
{
    int i;
    struct cpio_info info;
    char *filename;
    unsigned long file_sz;
    void *file_buf;
    int extract_file;
    char *path;
    int dir_len = strlen(dir);

    cpio_info(archive, &info);
    if ((path = (char *)KMemAlloc(sizeof(char) * (info.max_path_sz + dir_len + 1))) == NULL)
    {
        KPrint("[fsal] mem alloc for cpio path error!\n");
        return -1;
    }

    for (i = 0; i < info.file_count; ++i)
    {
        file_buf = cpio_get_entry(archive, i, (const char **)&filename, &file_sz);
        strcpy(path, dir);
        strcpy(path + dir_len, filename);

        // Default directory instead of file
        if (file_sz == 0)
        {
            KFileMkDir(path, O_RDWR);
        }
        else
        {
            extract_file = KFileOpen(path, O_CREATE | O_RDWR);
            if (extract_file < 0)
            {
                KPrint("[fsal] open %s failed!\n", path);
                return -1;
            }

            if (KFileWrite(extract_file, file_buf, file_sz) < 0)
            {
                KPrint("[fsal] write %s failed!\n", path);
                return -1;
            }
            KFileClose(extract_file);
        }
    }
    KMemFree(path);
}

int FsalDiskMountInit()
{
#if !CONFIG_CD
    if (!FsalDiskMount("/dev/hd", DISK_SOLT_NUM))
        return 0;
    if (!FsalDiskMount("/dev/sd", DISK_SOLT_NUM))
        return 0;
#else
    // create fat32 fs on ram disk
    if (!(fsif.mount("/dev/ram0", ROOT_DIR_PATH, "fat32", MOUNT_FORMAT)))
    {
        void *initrd_buff = NULL;

        if (!(initrd_buff = module_info_find(KERNEL_VMM_BASE, MODULE_INITRD)))
        {
            goto failed;
        }
        KPrint("[fsal] found initrd on %x\n", initrd_buff);
        // read initrd to ram disk
        CpioExtractFromMemory(initrd_buff, "/");
        KPrint("[fsal] mount device initrd to " ROOT_DIR_PATH "\n");
        // FsalListDir("/");
        return 0;
    }
#endif
failed:
    return -1;
}

int FsalDiskMount(char *path, int disk_max)
{
    char name[MAX_PATH_LEN+1];
    int i, j;
    char s[3] = {0};
    char *p = NULL;
    int flags = -1;

    for (i = 0; i < disk_max; i++) // disk
    {
        if (flags != -1)
            break;

        memset(name, 0, MAX_PATH_LEN);
        strcpy(name, path);
        s[0] = '0' + i;
        s[1] = '\0';
        strcat(name, s);
        p = name + strlen(name);
        for (j = 0; j < DISK_PARITION_MAX; j++) // partiton
        {
            s[0] = 'p';
            s[1] = '0' + j;
            s[2] = '\0';
            strcpy(p, s);
            KPrint("[fsal] mount %s", name);
            if (!fsif.mount(name, ROOT_DIR_PATH, "fat32", 0))
            {
                KPrint("Fsal: mount device %s to path %s success!\n", name, ROOT_DIR_PATH);
                flags = 1;
                break;
            }
        }
    }

    if (i >= disk_max)
    {
        KPrint("fsal: mount path %s to %s failed!\n", path, ROOT_DIR_PATH);
        return -1;
    }
    return 0;
}

int FsalListDir(char *path)
{
    dirent_t dirent;
    int i;
    int dir = fsif.opendir(path);
    if (dir >= 0)
    {
        while (1)
        {
            memset(&dirent, 0, sizeof(dirent_t));

            if (fsif.readdir(dir, &dirent) < 0) // reach to end of dir
            {
                return dir;
            }

            /*if (dirent.attr & DIRENT_DIR)
            {
                if (path[0] = '/' && path[1] != '\0')
                    KPrint("%s/%s\n", path, dirent.name);
                else
                    KPrint("%s%s\n", path, dirent.name);
                // make new dir and search
                i = strlen(path);
                sprintf(path + i, "/%s", dirent.name);
                if (FsalListDir(path) < 0)
                {
                    break; // no sub dir
                }
                path[i + 1] = '\0'; // rollback path to level up
            }
            else*/
            {
                if (path[0] == '/' && path[1] != '\0')
                    KPrint("%s/%s size=%d\n", path, dirent.name, (uint32_t)dirent.size);
                else
                    KPrint("%s%s size=%d\n", path, dirent.name, (uint32_t)dirent.size);
            }
        }
        fsif.closedir(dir);
    }
    return dir;
}


//#define DEBUG_MOUNT

// SystemMountInfo Function
// mt: mount point buffer
// counthow much mount points to get
// count==0 and mt==NULL: return FSAL_PATH_MAX
// return: succeed get mount points count,failed return -1
int SysMountInfo(mount_t *mt, uint32_t count)
{
    if (count > FSAL_PATH_LEN)
        return -1;

    if (count == 0 && !mt)
    {
        return FSAL_PATH_LEN;
    }

    if (!mt || SafetyCheckRange(mt, (uint64_t)(count * sizeof(mount_t))))
        return -1;

    if (count == 1) // get one mount point
    {
        fsal_path_t *fpath = &fsal_path_table[0];
        if (!fpath || fpath->fsal == NULL)
            return 0;
#ifdef DEBUG_MOUNT
        KPrint("dev %s path %s alpath %s\n", fpath->devpath, fpath->path, fpath->alpath);
#endif

        strcpy(mt->path, fpath->path);
        strcpy(mt->abspath, fpath->alpath);
        strcpy(mt->devpath, fpath->devpath);
        return count;
    }
    else // get all mount point
    {
        mount_t *_mt = mt;
        fsal_path_t *path = fsal_path_table;
        uint32_t _c = count;
        for (int i = 0; i<FSAL_PATH_MAX, _c> 0; i++, _c--)
        {
            _mt = mt + i;
            path = path + i;
            if (path->fsal == NULL)
                return count - _c;
#ifdef DEBUG_MOUNT
            KPrint("dev %s path %s alpath %s\n", path->devpath, path->path, path->alpath);
#endif
            strcpy(_mt->path, path->path);
            strcpy(_mt->abspath, path->alpath);
            strcpy(_mt->devpath, path->devpath);
        }
        return count;
    }
}